Website Mirror Script 🌐

Website Mirror Script 🌐
拾璃Website Mirror Script 🌐
这是一个Python 脚本,用于创建指定网站的本地离线镜像。它会从一个起始 URL 开始,递归地下载所有同域名下的页面、样式表、脚本和图片,并重写链接以便在本地进行浏览。
完成镜像后,脚本会自动启动一个本地 Web 服务器,让您可以方便地在浏览器中预览结果。
功能特性 ✨
- 递归下载: 从一个入口 URL 开始,自动抓取整个站点的结构和资源。
- 链接重写: 智能地将页面中的绝对链接(如
href,src)转换为相对链接,确保镜像站点可以完全离线工作。 - 资源处理: 能够下载 HTML、CSS、JavaScript 文件以及图片等多种资源类型。
- 遵守
robots.txt: 可配置是否遵守目标网站的robots.txt爬虫协议,做一个“有礼貌的”爬虫。 - 支持抓取延迟: 如果
robots.txt中定义了Crawl-delay,脚本会自动遵守,避免对服务器造成过大压力。 - 内置 Web 服务器: 镜像完成后,一键启动本地服务器,立即在
http://localhost:PORT查看效果。 - 路径清理: 自动处理 URL 中的特殊字符,将其转换为安全的文件名和路径。
环境要求 📦
- Python 3.x
- 第三方库:
requests和beautifulsoup4
您可以使用 pip 轻松安装这些依赖:
1 | pip install requests beautifulsoup4 |
文件说明 📄
requirements.txt: 列出项目依赖的 Python 库。website-mirror.py: 主脚本文件,包含网站镜像的逻辑。
使用方法 🚀
- 安装依赖: 运行
pip install -r requirements.txt安装所需的 Python 库。 - 配置脚本: 打开
website-mirror.py文件,修改以下配置:TARGET_URL: 替换为您想要镜像的网站 URL (例如:"https://www.example.com").DOWNLOAD_DIR: 设置下载目录的名称 (默认为"mirrored_site").SERVER_PORT: 选择本地 Web 服务器使用的端口 (默认为712).RESPECT_ROBOTS_TXT: 设置为True以遵守robots.txt协议, 或False以忽略.
- 运行脚本: 在命令行中执行
python website-mirror.py。 - 预览镜像: 脚本完成后, 会自动启动一个本地 Web 服务器. 在浏览器中访问
http://localhost:PORT(将PORT替换为您设置的端口号) 即可预览镜像的网站。
核心代码解析 🧩
让我们深入了解这个工具背后的核心代码 (website-mirror.py),重点分析两个核心函数:
download_and_process_url(url_to_fetch, base_domain, download_root)该函数负责下载指定 URL 的内容,并对 HTML 文件进行处理,提取链接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71def download_and_process_url(url_to_fetch, base_domain, download_root):
"""下载URL内容,如果是HTML则解析并查找更多链接。返回True表示成功,False表示失败。"""
print(f"[*] 正在访问: {url_to_fetch}")
try:
response = session.get(url_to_fetch, timeout=15, stream=True) # Increased timeout
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"[!] 请求失败 {url_to_fetch}: {e}")
visited_urls.add(url_to_fetch) # 标记为已访问,即使失败,以避免重试
return False
content_type = response.headers.get('content-type', '').lower()
local_filepath, local_dir = get_local_path(base_domain, url_to_fetch, download_root)
local_dir.mkdir(parents=True, exist_ok=True)
with open(local_filepath, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f" -> 已保存到: {local_filepath}")
visited_urls.add(url_to_fetch) # 标记为已访问(成功下载后)
if 'text/html' in content_type:
encoding = response.encoding or 'utf-8'
try:
with open(local_filepath, 'r', encoding=encoding, errors='replace') as f_read:
soup = BeautifulSoup(f_read.read(), 'html.parser')
except Exception as e:
print(f"[!] 解析HTML失败 {local_filepath}: {e}")
return True # 文件已下载,但解析失败
modified = False
for tag_name, attr_name in [('a', 'href'), ('link', 'href'),
('img', 'src'), ('script', 'src')]:
for tag in soup.find_all(tag_name):
attr_value = tag.get(attr_name)
if not attr_value:
continue
absolute_url = urljoin(url_to_fetch, attr_value)
parsed_absolute_url = urlparse(absolute_url)
if parsed_absolute_url.netloc == base_domain and \
parsed_absolute_url.scheme in ['http', 'https']:
target_local_path_obj, _ = get_local_path(base_domain, absolute_url, download_root)
current_file_dir_obj = local_filepath.parent
try:
relative_new_path = os.path.relpath(target_local_path_obj, current_file_dir_obj)
relative_new_path = relative_new_path.replace(os.sep, '/')
if tag[attr_name] != relative_new_path:
# print(f" 🔗 更新链接: {tag[attr_name]} -> {relative_new_path}")
tag[attr_name] = relative_new_path
modified = True
except ValueError as ve:
print(f"[!] 无法计算相对路径: {target_local_path_obj} from {current_file_dir_obj} - {ve}")
if absolute_url not in visited_urls and absolute_url not in urls_to_visit:
urls_to_visit.add(absolute_url)
if modified:
try:
with open(local_filepath, 'w', encoding=encoding, errors='replace') as f_write:
f_write.write(str(soup))
print(f" -> 已更新链接并保存: {local_filepath}")
except Exception as e:
print(f"[!] 写入修改后的HTML失败 {local_filepath}: {e}")
return True- 核心逻辑: 下载 URL 内容,如果是 HTML 则解析并查找更多链接,并将链接转换为相对路径。
- 依赖: 使用
requests库发送 HTTP 请求,使用BeautifulSoup库解析 HTML。 - 链接转换: 使用
urljoin将相对 URL 转换为绝对 URL,使用os.path.relpath计算相对路径,并更新 HTML 标签的属性值。
start_mirroring(target_url_str, download_dir_str)该函数是主镜像逻辑的入口点,负责递归地下载和处理 URL。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53def start_mirroring(target_url_str, download_dir_str):
"""主镜像逻辑"""
download_root_path = Path(download_dir_str)
if download_root_path.exists():
print(f"[*] 清理旧的镜像目录: {download_dir_str}")
shutil.rmtree(download_root_path)
download_root_path.mkdir(parents=True, exist_ok=True)
parsed_target_url = urlparse(target_url_str)
base_domain = parsed_target_url.netloc
user_agent_for_robots = session.headers.get("User-Agent", "*")
if not base_domain:
print(f"[!] 无效的目标URL,无法提取域名: {target_url_str}")
return
urls_to_visit.add(target_url_str)
while urls_to_visit:
current_url = urls_to_visit.pop()
if current_url in visited_urls:
continue
current_domain = urlparse(current_url).netloc # Should always be base_domain
if RESPECT_ROBOTS_TXT:
rp = get_robot_parser_for_url(current_url)
if not rp.can_fetch(user_agent_for_robots, current_url):
print(f"[*] 跳过 (robots.txt禁止): {current_url}")
visited_urls.add(current_url) # 标记为已访问,避免重试
continue
delay = rp.crawl_delay(user_agent_for_robots)
if delay:
last_request_time = last_request_times.get(current_domain, 0)
wait_time = (last_request_time + delay) - time.time()
if wait_time > 0:
print(f" [i] 遵守Crawl-delay: 等待 {wait_time:.2f}s ({current_domain})")
time.sleep(wait_time)
download_successful = download_and_process_url(current_url, base_domain, download_root_path.resolve())
if RESPECT_ROBOTS_TXT and download_successful: # 记录成功请求的时间
last_request_times[current_domain] = time.time()
# 可选:即使不遵守robots.txt,也加一个小延时
# if not (RESPECT_ROBOTS_TXT and delay): # 如果没有 crawl-delay 控制
# time.sleep(0.1) # 通用小延时
print("\n[*] 镜像过程完成!")
print(f"[*] 文件保存在: {download_root_path.resolve()}")- 核心逻辑: 使用
urls_to_visit集合维护待下载的 URL 列表,使用visited_urls集合记录已下载的 URL,避免重复下载。 循环从urls_to_visit中取出一个 URL,调用download_and_process_url函数下载和处理内容。 robots.txt支持: 如果RESPECT_ROBOTS_TXT为True,则先检查robots.txt文件,判断是否允许抓取该 URL。- 域名限制: 只下载与目标 URL 同域名的 URL。
- 核心逻辑: 使用
其他重要函数
get_local_path(base_url_netloc, current_url_str, download_dir_base): 根据 URL 生成本地文件路径。run_server(port, directory): 启动一个简单的 HTTP 服务器来查看镜像效果。
注意事项 ⚠️
robots.txt: 请尊重网站的robots.txt协议。不遵守robots.txt可能会导致您的 IP 被网站封禁。- 网站结构: 对于复杂的网站,镜像可能不完整。
- 免责声明: 此脚本仅用于学习和研究目的。请勿用于非法用途。
总结 🎉
这个 Python 脚本提供了一个简单而强大的方法来创建网站的本地镜像。 它可以帮助您在没有网络连接的情况下浏览网站,或用于备份和归档网站内容。
声明: 本博客文章中如有任何涉及版权的内容,请立即联系 admin@main.712521.xyz,我们将尽快处理。




